package com.clei.utils;

import com.clei.dto.db.ChildTableInfo;
import com.clei.dto.db.ColumnProperty;
import com.clei.dto.db.TDEngineExportConfig;
import com.clei.dto.db.TableInfo;
import com.clei.enums.DatabaseServerEnum;
import com.clei.utils.helper.DBHelper;
import com.clei.utils.helper.TimeRecorder;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * tdengine 数据导出 util
 *
 * @author Y
 * @date 2024-01-09
 **/
public class TDEngineExportUtil {

    /**
     * db
     */
    private final static DatabaseServerEnum DB_SERVER = DatabaseServerEnum.TAOS;

    /**
     * deHelper
     */
    private final static DBHelper DB_HELPER = DB_SERVER.getDbHelper();

    /**
     * 查询超级表sql
     * SHOW STABLES;亦可
     */
    private final static String SELECT_STABLE_SQL
            = "SELECT stable_name FROM information_schema.ins_stables where db_name = '%s' ORDER BY stable_name ASC;";

    /**
     * 查询表sql
     * SHOW TABLES;亦可
     */
    private final static String SELECT_TABLE_SQL
            = "SELECT table_name FROM information_schema.ins_tables where db_name = '%s' AND stable_name = '%s' ORDER BY stable_name ASC;";

    /**
     * 查询tag sql
     */
    private final static String SELECT_TAG_SQL
            = "SELECT table_name,tag_name,tag_type,tag_value FROM information_schema.ins_tags WHERE db_name = '%s' AND table_name IN (%s);";

    /**
     * 建表sql
     * 子表
     */
    private final static String CREATE_TABLE_SQL = "CREATE TABLE `%s` USING `%s` (%s) TAGS (%s);";

    /**
     * 查询数据sql
     */
    private final static String SELECT_DATA_SQL = "SELECT * FROM `%s` %s;";

    /**
     * 创建表sql
     */
    private final static String SHOW_TABLE_SQL = "SHOW CREATE TABLE `%s`.`%s`;";

    /**
     * TimeRecorder
     */
    private static final TimeRecorder RECORDER = new TimeRecorder();

    public static void main(String[] args) {
        TDEngineExportConfig exportConfig = new TDEngineExportConfig();
        ReadUtil.toEntity(TDEngineExportConfig.getPropertyList(), exportConfig);
        exportConfig.assemble();
        MybatisUtil.execMultipleOperation(DB_SERVER, () -> export(exportConfig));
        RECORDER.printAll();
    }

    /**
     * 导出
     *
     * @param exportConfig 导出配置
     */
    public static void export(TDEngineExportConfig exportConfig) {
        Map<String, TableInfo> sTableMap = getSTableMap(exportConfig.getDatabase(), exportConfig.getsTableList());
        RECORDER.record("超表信息获取");
        if (CollectionUtil.isEmpty(sTableMap)) {
            PrintUtil.log("未查询到超表");
            return;
        }
        long timestamp = System.currentTimeMillis();
        Integer exportType = exportConfig.getExportTypeValue();
        String exportPath = exportConfig.getExportPath();
        Map<String, ChildTableInfo> tableMap = getTableMap(exportConfig, sTableMap.values());
        RECORDER.record("子表信息获取");
        // 结构
        if (DB_HELPER.exportStructure(exportType)) {
            List<String> lineList = new ArrayList<>(3 + sTableMap.size() + tableMap.size());
            lineList.add("-- " + DateUtil.currentDateTime(true));
            lineList.add("-- sTable structure count : " + sTableMap.size());
            sTableMap.forEach((k, v) -> lineList.add(v.getCreateSql()));
            lineList.add("-- table structure count : " + tableMap.size());
            tableMap.forEach((k, v) -> lineList.add(v.getCreateSql()));
            FileUtil.writeTxt(exportPath + File.separator + DB_SERVER + "_structure_" + timestamp + ".sql", lineList);
            RECORDER.record("导出结构");
        }
        // 数据
        if (!DB_HELPER.exportData(exportType)) {
            return;
        }
        if (CollectionUtil.isNotEmpty(tableMap)) {
            String where = StringUtil.isBlank(exportConfig.getWhere()) ? "" : "WHERE " + exportConfig.getWhere();
            String filePath = exportPath + File.separator + DB_SERVER + "_data_" + timestamp + ".sql";
            int insertLimit = 500;
            FileUtil.writeTxt(filePath, Collections.singletonList("-- " + DateUtil.currentDateTime(true)), true);
            tableMap.forEach((k, v) -> {
                String sTableName = v.getsTableName();
                TableInfo tableInfo = sTableMap.get(sTableName);
                String valueTemplate = tableInfo.getInsertValues();
                List<Object[]> tableData = getTableData(v, tableInfo, where);
                List<List<Object[]>> lists = CollectionUtil.cut(tableData, insertLimit);
                // 写入

                for (List<Object[]> dataList : lists) {
                    List<String> lineList = new ArrayList<>(2);
                    lineList.add("-- data " + k + " count : " + dataList.size());
                    // tdengine批量插入语句不能换行
                    String insertPrefix = String.format(tableInfo.getInsertPrefix(), k);
                    String values = dataList.stream().map(d -> String.format(valueTemplate, d)).collect(Collectors.joining(","));
                    lineList.add(insertPrefix + values + ";");
                    // append
                    FileUtil.writeTxt(filePath, lineList, true);
                }
            });
        }
        RECORDER.record("导出数据");
    }

    /**
     * 获取超级表信息
     *
     * @param database   数据库
     * @param sTableList 超级表
     * @return map
     */
    private static Map<String, TableInfo> getSTableMap(String database, List<String> sTableList) {
        // 超级表筛选
        String selectSql = String.format(SELECT_STABLE_SQL, database);
        List<String> sTableNameList = MybatisUtil.commonMultipleOperation(mapper -> mapper.selectString(selectSql));
        if (!CollectionUtil.isEmpty(sTableList) && !CollectionUtil.isEmpty(sTableNameList)) {
            Set<String> stableSet = new HashSet<>(sTableList);
            sTableNameList = sTableNameList.stream().filter(stableSet::contains).collect(Collectors.toList());
        }
        // 使用LinkedHashMap保证顺序
        Map<String, TableInfo> resMap = new LinkedHashMap<>(sTableNameList.size());
        // 表创建语句 列属性数据
        for (String stable : sTableNameList) {
            String tableCreateSql = getTableCreateSql(database, stable);
            List<ColumnProperty> columnList = getColumnList(stable, tableCreateSql);
            resMap.put(stable, new TableInfo(DB_SERVER, stable, tableCreateSql, columnList));
        }
        return resMap;
    }

    /**
     * 获取表信息
     *
     * @param exportConfig 导出配置
     * @param sTableList   超级表
     */
    private static Map<String, ChildTableInfo> getTableMap(TDEngineExportConfig exportConfig, Collection<TableInfo> sTableList) {
        List<String> tableList = exportConfig.getTableList();
        Set<String> tableSet = CollectionUtil.isEmpty(tableList) ? Collections.emptySet()
                : tableList.stream().map(String::toLowerCase).collect(Collectors.toSet());
        // resMap tableName -> tableInfo map
        Map<String, ChildTableInfo> resMap = new LinkedHashMap<>();
        for (TableInfo sTable : sTableList) {
            String sTableName = sTable.getTableName();
            // 表筛选
            String selectSql = String.format(SELECT_TABLE_SQL, exportConfig.getDatabase(), sTableName);
            List<String> tableNameList = MybatisUtil.commonMultipleOperation(mapper -> mapper.selectString(selectSql));
            if (CollectionUtil.isNotEmpty(tableNameList)) {
                if (CollectionUtil.isNotEmpty(tableSet)) {
                    tableNameList = tableNameList.stream().filter(tableSet::contains).collect(Collectors.toList());
                }
                for (String tableName : tableNameList) {
                    resMap.put(tableName, new ChildTableInfo(tableName, sTableName));
                }
            }
        }
        // 导出结构
        if (DB_HELPER.exportStructure(exportConfig.getExportTypeValue()) && CollectionUtil.isNotEmpty(resMap)) {
            String tableNames = resMap.keySet().stream().map(s -> '\'' + s + '\'').collect(Collectors.joining(","));
            String selectSql = String.format(SELECT_TAG_SQL, exportConfig.getDatabase(), tableNames);
            List<Map<String, Object>> tagList = MybatisUtil.commonMultipleOperation(mapper -> mapper.selectMap(selectSql));
            Map<String, List<Map<String, Object>>> tagMap = tagList.stream()
                    .collect(Collectors.groupingBy(m -> DB_HELPER.getString(m.get("table_name"))));
            Map<String, Map<String, Object>> tagValueMap = new HashMap<>(tagMap.size());
            tagMap.forEach((k, v) -> {
                Map<String, Object> tagValue = new HashMap<>(v.size());
                for (Map<String, Object> m : v) {
                    tagValue.put(DB_HELPER.getString(m.get("tag_name")), m.get("tag_value"));
                }
                tagValueMap.put(k, tagValue);
            });
            // sTableName - > tagNameList map
            Map<String, List<String>> tagNameMap = new HashMap<>(sTableList.size());
            Map<String, String> tagNamesMap = new HashMap<>(sTableList.size());
            for (TableInfo t : sTableList) {
                String createSql = t.getCreateSql();
                int i = createSql.indexOf("TAGS");
                String tagsSql = createSql.substring(i);
                tagsSql = tagsSql.substring(tagsSql.indexOf('(') + 1, tagsSql.lastIndexOf(')'));
                List<String> tagNameList = Arrays.stream(tagsSql.split(","))
                        .map(s -> s.trim().split(" ")[0].replaceAll("`", "")).collect(Collectors.toList());
                tagNameMap.put(t.getTableName(), tagNameList);
                tagNamesMap.put(t.getTableName(), tagNameList.stream().map(s -> '`' + s + '`').collect(Collectors.joining(",")));
            }
            resMap.forEach((k, v) -> {
                String sTableName = v.getsTableName();
                String tagNames = tagNamesMap.get(sTableName);
                List<String> tagNameList = tagNameMap.get(sTableName);
                Map<String, Object> tagEntityMap = tagValueMap.get(k);
                String tagValues = tagNameList.stream().map(t -> DB_HELPER.getStringForInsert(tagEntityMap.get(t)))
                        .collect(Collectors.joining(","));
                String createTableSql = String.format(CREATE_TABLE_SQL, k, sTableName, tagNames, tagValues);
                v.setCreateSql(createTableSql);
            });
        }
        return resMap;
    }

    /**
     * 获取表数据
     *
     * @param childTableInfo 子表信息
     * @param tableInfo      超表信息
     * @param where          WHERE筛选语句
     */
    private static List<Object[]> getTableData(ChildTableInfo childTableInfo, TableInfo tableInfo, String where) {
        String dataSql = String.format(SELECT_DATA_SQL, childTableInfo.getTableName(), where);
        List<Map<String, Object>> dataList = MybatisUtil.commonMultipleOperation(mapper -> mapper.selectMap(dataSql));
        List<ColumnProperty> columnList = tableInfo.getColumnList();
        int columnSize = columnList.size();
        return dataList.stream()
                .map(m -> {
                    Object[] data = new Object[columnSize];
                    int i = 0;
                    for (ColumnProperty column : columnList) {
                        Object obj = m.get(column.getColumnName());
                        data[i++] = DB_HELPER.getStringForInsert(obj);
                    }
                    return data;
                }).collect(Collectors.toList());
    }

    /**
     * 获取表创建sql
     *
     * @param database  数据库
     * @param tableName 表名
     * @return map
     */
    private static String getTableCreateSql(String database, String tableName) {
        String showCreateSql = String.format(SHOW_TABLE_SQL, database, tableName);
        List<Map<String, Object>> createSqlMap = MybatisUtil.commonMultipleOperation(mapper -> mapper.selectMap(showCreateSql));
        String tableSql = null;
        if (CollectionUtil.isEmpty(createSqlMap)) {
            PrintUtil.log("获取表创建SQL失败 tableName : {}", tableName);
        } else {
            for (Map<String, Object> s : createSqlMap) {
                Object createObj = s.get("Create Table");
                byte[] sqlBytes = (byte[]) createObj;
                tableSql = new String(sqlBytes) + ';';
            }
        }
        return tableSql;
    }

    /**
     * getColumnList
     *
     * @param tableName 表名
     * @param tableSql  建表sql
     * @return 列
     */
    private static List<ColumnProperty> getColumnList(String tableName, String tableSql) {
        // 去除反引号
        tableSql = tableSql.replaceAll("`", "");
        int startIndex = tableSql.indexOf(tableName);
        int endIndex = tableSql.lastIndexOf("TAGS");
        // 截取列sql
        String columnSql = tableSql.substring(startIndex + tableName.length(), endIndex);
        columnSql = columnSql.trim();
        // 去首尾括号
        columnSql = columnSql.substring(1, columnSql.length() - 1);
        // 分隔 转换
        return Arrays.stream(columnSql.split(", ")).map(s -> {
            String[] arr = s.split(" ");
            String columnType = arr[1].toLowerCase();
            return new ColumnProperty(arr[0].toLowerCase(), DB_HELPER.getJavaType(columnType), null);
        }).collect(Collectors.toList());
    }
}
